'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:# www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''

'''
 * Base class for all date/time/duration type representations.
 * Includes support functions for parsing and comparing dates.
'''
from base.datatypes.xsd.XSDDatatype import XSDDatatype
from base.exception.NumberFormatException import NumberFormatException
from base.exception.RuntimeException import RuntimeException

class XSDAbstractDateTimeType(XSDDatatype):

    '''
     * Constructor
    '''
    # param String
    # return
    def __init__(self, typename):
        super().__init__(typename)
    

    '''
     * Compares two instances of values of the given datatype.
     * This ignores lang tags and just uses the java.lang.Number
     * equality.
    '''
    #@Override
    # param LiteralLabel LiteralLabel
    # return boolean
    def isEqual(self, value1, value2):
        return value1.getValue().equals(value2.getValue())
    

    '''
     * Mask to indicate whether year is present
    '''
    # param
    # return short
    YEAR_MASK = 0x1

    '''
     * Mask to indicate whether month is present
    '''
    # param
    # return short
    MONTH_MASK = 0x2

    '''
     * Mask to indicate whether day is present
    '''
    # param
    # return short
    DAY_MASK = 0x4

    '''
     * Mask to indicate whether time is present
    '''
    # param
    # return short
    TIME_MASK = 0x8

    '''
     * Mask to indicate all date/time are present
    '''
    # param
    # return short
    FULL_MASK = 0xf


#  --------------------------------------------------------------------
#  This code is adapated from Xerces 2.6.0 AbstractDateTimeDV.    
#  Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
#  reserved.
#  --------------------------------------------------------------------

    # define constants
    # protected final static int

    CY = 0
    M = 1
    D = 2
    h = 3
    m = 4
    s = 5
    ms = 6
    msscale = 8
    utc = 7
    hh = 0
    mm = 1

    # size for all objects must have the same fields:
    # CCYY, MM, DD, h, m, s, ms + timeZone
    TOTAL_SIZE = 9

    # define constants to be used in assigning default values for
    # all date/time excluding duration
    YEAR = 2000
    # TODO 原来是 01 ?
    MONTH = 1
    DAY = 15


    '''
     * Parses time hh:mm:ss.sss and time zone if any
     *
     * @param start
     * @param end
     * @param data
     * @throws RuntimeException
    '''
    # param String int int int[] int[]
    # return void
    def getTime(self, buffer, start, end, data, timeZone):
        if not isinstance(data, list) or not isinstance(timeZone, list):
            raise RuntimeException("data or timeZone must be list")

        # int
        stop = start + 2

        global h
        global m
        global s

        # get hours (hh)
        data[h] = self.parseInt(buffer, start, stop)

        # get minutes (mm)

        if buffer[stop] != ':':
            raise RuntimeException("Error in parsing time zone")
        stop = stop + 1

        start = stop
        stop = stop + 2
        data[m] = self.parseInt(buffer, start, stop)

        # get seconds (ss)
        if (buffer[stop] != ':'):
            raise RuntimeException("Error in parsing time zone")
        stop = stop + 1

        start = stop
        stop = stop + 2
        data[s] = self.parseInt(buffer, start, stop)

        if (stop == end):
            return

        # get miliseconds (ms)
        start = stop
        # int
        millisec = start if buffer[start] == '.' else -1

        # find UTC sign if any
        # int
        sign = self.findUTCSign(buffer, start, end)

        global ms
        global msscale

        # parse miliseconds
        if (millisec != -1):
            # The end of millisecond part is between . and
            # either the end of the UTC sign
            start = end if sign < 0 else sign
            # int
            msEnd = start
            while (buffer[msEnd - 1] == '0'):
                msEnd = msEnd - 1
            data[ms] = self.parseInt(buffer, millisec + 1, msEnd)
            data[msscale] = msEnd - millisec - 1
        

        # parse UTC time zone (hh:mm)
        if (sign > 0):
            if (start != sign):
                raise RuntimeException("Error in parsing time zone")
            self.getTimeZone(buffer, data, sign, end, timeZone)
        elif (start != end):
            raise RuntimeException("Error in parsing time zone")
        
    

    '''
     * Parses date CCYY-MM-DD
     *
     * @param start
     * @param end
     * @param data
     * @throws RuntimeException
    '''
    # param String int int int[]
    # return int
    # throws RuntimeException
    def getDate(self, buffer, start, end, date):
        start = self.getYearMonth(buffer, start, end, date)

        if (buffer[start] != '-'):
            raise RuntimeException("CCYY-MM must be followed by '-' sign")
        start = start + 1

        # int
        stop = start + 2
        global D
        date[D] = self.parseInt(buffer, start, stop)
        return stop
    

    '''
     * Parses date CCYY-MM
     *
     * @param start
     * @param end
     * @param data
     * @throws RuntimeException
    '''
    # param String int int int[]
    # return int
    # throws RuntimeException
    def getYearMonth(self, buffer, start, end, date):
        if (buffer[0] == '-'):
            # REVISIT: date starts with preceding '-' sign
            #          do we have to do anything with it?
            # 
            start = start + 1

        # int
        i = self.indexOf(buffer, start, end, '-')
        if (i == -1):
                raise RuntimeException("Year separator is missing or misplaced")
        # int
        length = i - start
        if (length < 4):
            raise RuntimeException("Year must have 'CCYY' format")
        elif (length > 4 and buffer[start] == '0'):
            raise RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits otherwise they are forbidden")

        global CY
        date[CY] = self.parseIntYear(buffer, i)
        if (buffer[i] != '-'):
            raise RuntimeException("CCYY must be followed by '-' sign")
        
        start = ++i
        i = start + 2
        global M
        date[M] = self.parseInt(buffer, start, i)
        return i # fStart points right after the MONTH
    

    '''
     * Shared code from Date and YearMonth datatypes.
     * Finds if time zone sign is present
     *
     * @param end
     * @param date
     * @throws RuntimeException
    '''
    # param String int int int[] int[]
    # return void
    #  throws RuntimeException
    def parseTimeZone(self, buffer, start, end, date, timeZone):
        # fStart points right after the date
        if (start < end):
            # int
            sign = self.findUTCSign(buffer, start, end)
            if (sign < 0):
                raise RuntimeException("Error in month parsing")
            else:
                self.getTimeZone(buffer, date, sign, end, timeZone)
            
        
    

    '''
     * Parses time zone: 'Z' or:+,- followed by  hh:mm
     *
     * @param data
     * @param sign
     * @throws RuntimeException
    '''
    # param String int[] int int int[]
    # return void
    # throws RuntimeException
    def getTimeZone(self, buffer, data, sign, end, timeZone):
        if isinstance(data, list) or isinstance(timeZone, list):
            raise RuntimeException("data or timeZone must be list")
        global utc
        global hh
        global mm
        data[utc] = buffer[sign]

        if (buffer[sign] == 'Z'):
            sign = sign + 1
            if (end > sign):
                raise RuntimeException("Error in parsing time zone")
            return None
        
        if (sign <= (end - 6)):
            # parse [hh]
            # int
            sign = sign + 1
            stop = sign + 2
            timeZone[hh] = self.parseInt(buffer, sign, stop)
            if (buffer[stop] != ':'):
                raise RuntimeException("Error in parsing time zone")
            stop = stop + 1

            # parse [ss]
            timeZone[mm] = self.parseInt(buffer, stop, stop + 2)

            if (stop + 2 != end):
                raise RuntimeException("Error in parsing time zone")
        else:
            raise RuntimeException("Error in parsing time zone")
        
    '''
     * Computes index of given char within StringBuffer
     *
     * @param start
     * @param end
     * @param ch    character to look for in StringBuffer
     * @return index of ch within StringBuffer
    '''
    # param String int int char
    # return int
    def indexOf(self, buffer, start, end, ch):
        i = start
        while i < end:
            if (buffer[i] == ch):
                return i
            i = i + 1
        return -1


    # check whether the character is in the range 0x30 ~ 0x39
    # param char
    # return boolean
    def isDigit(self, ch):
        return ch >= '0' and ch <= '9'
    

    # if the character is in the range 0x30 ~ 0x39, return its int value (0~9),
    # otherwise, return -1
    # param char
    # return int
    def getDigit(self, ch):
        return ch - '0' if self.isDigit(ch) else -1
    


    '''
     * Return index of UTC char: 'Z', '+', '-'
     *
     * @param start
     * @param end
     * @return index of the UTC character that was found
    '''
    # param String int int
    # return int
    def findUTCSign(self, buffer, start, end):
        c = None
        i = start
        while i < end:
            c = buffer[i]
            if (c == 'Z' or c == '+' or c == '-'):
                return i
            i = i + 1
        return -1

    '''
     * Given start and end position, parses string value
     *
     * @param value string to parse
     * @param start Start position
     * @param end   end position
     * @return return integer representation of characters
    '''
    # param String int int
    # return int
    # throws NumberFormatException:
    def parseInt(self, buffer, start, end):
        # REVISIT: more testing on this parsing needs to be done.
        # 以下6个都是int
        radix = 10
        result = 0
        digit = 0
        limit = -1 * 0x7fffffff
        multmin = limit / radix
        i = start
        while True:
            digit = self.getDigit(buffer[i])
            if (digit < 0):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            if (result < multmin):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            result *= radix
            if (result < limit + digit):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            result -= digit
            i = i + 1
            if i >= end:
                break
        return -result
    

    # parse Year differently to support negative value.
    # param String int
    # return int
    def parseIntYear(self, buffer, end):
        # boolean
        negative = False
        # 以下几个都是int
        radix = 10
        result = 0
        i = 0
        limit = None
        multmin = None
        digit = 0

        if (buffer[0] == '-'):
            negative = True
            limit = 0x80000000
            i = i + 1
        else:
            limit = -1 * 0x7fffffff
        
        multmin = limit / radix
        while (i < end):
            digit = self.getDigit(buffer[i])
            i = i + 1
            if (digit < 0):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            if (result < multmin):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            result *= radix
            if (result < limit + digit):
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
            result -= digit
        

        if (negative):
            if (i > 1):
                return result
            else:
                raise NumberFormatException("'" + buffer.toString() + "' has wrong format")
        
        return -result

    

    # param int[]
    # return String
    def dateToString(date):
        pass
        # global CY
        # global M
        # global D
        # global h
        # global m
        # global s
        # global utc
        # StringBuffer message = StringBuffer(25)
        # append(message, date[CY], 4)
        # message.append('-')
        # append(message, date[M], 2)
        # message.append('-')
        # append(message, date[D], 2)
        # message.append('T')
        # append(message, date[h], 2)
        # message.append(':')
        # append(message, date[m], 2)
        # message.append(':')
        # append(message, date[s], 2)
        # message.append('.')
        # appendFractionalTime(message, date[ms], date[msscale])
        # append(message, (char) date[utc], 0)
        # return message.toString()
    

    '''
     * Append the fraction time part of a date/time vector to
     * a string buffer.
    '''
    # param StringBuffer int int
    # return
    def appendFractionalTime(self, buff, fsec, scale):
        # String
        msString = str(fsec)
        # int
        pad = scale - msString.length()
        while (pad > 0):
            buff.append('0')
            pad = pad - 1

        # int
        trunc = msString.length()
        while (trunc > 0 and msString[trunc - 1] == '0'):
            trunc = trunc - 1
        buff.append(msString.substring(0, trunc))
    

    # param StringBuffer int int 
    # return void
    def append(self, message, value, nch):
        if (value < 0):
            message.append('-')
            value = -value
        
        if (nch == 4):
            if (value < 10):
                message.append("000")
            elif (value < 100):
                message.append("00")
            elif (value < 1000):
                message.append("0")
            message.append(value)
        elif (nch == 2):
            if (value < 10):
                message.append('0')
            message.append(value)
        else:
            if (value != 0):
                message.append(value)

    #  --------------------------------------------------------------------
    #  End of code is adapated from Xerces 2.6.0 AbstractDateTimeDV.
    #  --------------------------------------------------------------------

    '''
     * Normalization. If the value is narrower than the current data type
     * (e.g. value is xsd:date but the time is xsd:datetime) returns
     * the narrower type for the literal.
     * If the type is narrower than the value then it may normalize
     * the value (e.g. set the mask of an XSDDateTime)
     * Currently only used to narrow gener XSDDateTime objects
     * to the minimal XSD date/time type.
     *
     * @param value the current object value
     * @param dt    the currently set data type
     * @return a narrower version of the datatype based on the actual value range
    '''
    #@Override
    # param Object RDFDatatype
    # return RDFDatatype
    def normalizeSubType(self, value, dt):
        if isinstance(value, XSDDatatypeInstances.XSDDateTime):
            if (dt.equals(XSDDatatypeInstances.XSDdateTime)):
                pass
                # return ((XSDDateTime) value).getNarrowedDatatype()
            elif isinstance(dt, XSDDatatype):
                pass
                # We've externally narrowed the type, push this down to the date time
                # ((XSDDateTime) value).narrowType((XSDDatatype) dt)
        return self